package com.hotlcc.charles;

import lombok.NoArgsConstructor;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Formatter;
import java.util.Random;

/**
 * Charles 注册机
 */
@NoArgsConstructor
public class CharlesKeygen {
    private static final Random RANDOM = new SecureRandom();
    private static final long RC5KEY_NAME = 0x7a21c951691cd470L;
    private static final long RC5KEY_KEY = 0xb4f0e0ccec0eafadL;
    private static final int NAME_PREFIX = 0x54882f8a;

    private int calcPrefix(String name) {
        final byte[] bytes = name.getBytes(Charset.forName("UTF-8"));
        int length = bytes.length + 4;
        int padded = ((~length + 1) & (8 - 1)) + length;
        ByteBuffer input = ByteBuffer.allocate(padded).putInt(bytes.length).put(bytes);
        input.rewind();

        SimpleRC5 rc5 = new SimpleRC5(RC5KEY_NAME);
        ByteBuffer output = ByteBuffer.allocate(padded);
        while (input.hasRemaining()) {
            output.putLong(rc5.encrypt(input.getLong()));
        }
        output.rewind();

        int n = 0;
        for (byte b : output.array()) {
            n = rc5.rotateLeft(n ^ b, 0x3);
        }
        return n;
    }

    private int xor(final long n) {
        long n2 = 0L;
        for (int i = 56; i >= 0; i -= 8) {
            n2 ^= ((n >>> i) & 0xffL);
        }
        return Math.abs((int) (n2 & 0xffL));
    }

    private String key(int prefix, int suffix) {
        long in = ((long) prefix << 32);
        switch (suffix >> 16) {
            case 0x0401:
            case 0x0402:
            case 0x0403:
                in |= suffix;
                break;
            default:
                in |= (0x01000000 | (suffix & 0xffffff));
                break;
        }
        long out = new SimpleRC5(RC5KEY_KEY).decrypt(in);
        return new Formatter().format("%02x%016x", xor(in), out).toString();
    }

    public String keygen(String name) {
        if (name == null || name.isEmpty()) {
            throw new RuntimeException("Parameter 'name' cannot empty.");
        }
        return keygen(name, RANDOM.nextInt());
    }

    private String keygen(String name, int suffix) {
        int prefix = calcPrefix(name) ^ NAME_PREFIX;
        return key(prefix, suffix);
    }
}
